import { stringAt } from '../core/alphabet';
import { getFontSizePxByPt } from '../core/font';
import _cell from '../core/cell';
import { formulam } from '../core/formula';
import { formatm } from '../core/format';

import {
    Draw,
    DrawBox,
    thinLineWidth,
    npx,
} from '../canvas/draw';
// gobal var
const cellPaddingWidth = 5;
const tableFixedHeaderCleanStyle = { fillStyle: '#f4f5f8' };
const tableGridStyle = {
    fillStyle: '#fff',
    lineWidth: thinLineWidth,
    strokeStyle: '#e6e6e6',
};

function tableFixedHeaderStyle() {
    return {
        textAlign: 'center',
        textBaseline: 'middle',
        font: `500 ${npx(12)}px Source Sans Pro`,
        fillStyle: '#585757',
        lineWidth: thinLineWidth(),
        strokeStyle: '#e6e6e6',
    };
}

function getDrawBox(data, rindex, cindex, yoffset = 0) {
    const {
        left,
        top,
        width,
        height,
    } = data.cellRect(rindex, cindex);
    return new DrawBox(left, top + yoffset, width, height, cellPaddingWidth);
}
/*
function renderCellBorders(bboxes, translateFunc) {
  const { draw } = this;
  if (bboxes) {
    const rset = new Set();
    // console.log('bboxes:', bboxes);
    bboxes.forEach(({ ri, ci, box }) => {
      if (!rset.has(ri)) {
        rset.add(ri);
        translateFunc(ri);
      }
      draw.strokeBorders(box);
    });
  }
}
*/

export function renderCell(draw, data, rindex, cindex, yoffset = 0) {
    const { sortedRowMap, rows, cols } = data;
    if (rows.isHide(rindex) || cols.isHide(cindex)) return;
    let nrindex = rindex;
    if (sortedRowMap.has(rindex)) {
        nrindex = sortedRowMap.get(rindex);
    }

    const cell = data.getCell(nrindex, cindex);
    if (cell === null) return;
    let frozen = false;
    if ('editable' in cell && cell.editable === false) {
        frozen = true;
    }

    const style = data.getCellStyleOrDefault(nrindex, cindex);
    const dbox = getDrawBox(data, rindex, cindex, yoffset);
    dbox.bgcolor = style.bgcolor;
    if (style.border !== undefined) {
        dbox.setBorders(style.border);
        // bboxes.push({ ri: rindex, ci: cindex, box: dbox });
        draw.strokeBorders(dbox);
    }
    draw.rect(dbox, () => {
        // render text
        let cellText = _cell.render(cell.text || '', formulam, (y, x) => (data.getCellTextOrDefault(x, y)));
        if (cell.text && _cell.cellBiaoDa(cell.text) > 0) {
            data['rows']['_'][rindex]['cells'][cindex]['text_trueValue'] = cellText // 获取计算后值赋值给text_trueValue
        }
        if (style.format) {
            // console.log(data.formatm, '>>', cell.format);
            cellText = formatm[style.format].render(cellText);
        }
        const font = Object.assign({}, style.font);
        font.size = getFontSizePxByPt(font.size);
        // console.log('style:', style);
        draw.text(cellText, dbox, {
            align: style.align,
            valign: style.valign,
            font,
            color: style.color,
            strike: style.strike,
            underline: style.underline,
        }, style.textwrap);
        // error
        const error = data.validations.getError(rindex, cindex);
        if (error) {
            // console.log('error:', rindex, cindex, error);
            draw.error(dbox);
        }
        if (frozen) {
            draw.frozen(dbox);
        }
    });
}

function renderAutofilter(viewRange) {
    const { data, draw } = this;
    if (viewRange) {
        const { autoFilter } = data;
        if (!autoFilter.active()) return;
        const afRange = autoFilter.hrange();
        if (viewRange.intersects(afRange)) {
            afRange.each((ri, ci) => {
                const dbox = getDrawBox(data, ri, ci);
                draw.dropdown(dbox);
            });
        }
    }
}

function renderContent(viewRange, fw, fh, tx, ty) {
    const { draw, data } = this;
    draw.save();
    draw.translate(fw, fh)
        .translate(tx, ty);

    const { exceptRowSet } = data;
    // const exceptRows = Array.from(exceptRowSet);
    const filteredTranslateFunc = (ri) => {
        const ret = exceptRowSet.has(ri);
        if (ret) {
            const height = data.rows.getHeight(ri);
            draw.translate(0, -height);
        }
        return !ret;
    };

    const exceptRowTotalHeight = data.exceptRowTotalHeight(viewRange.sri, viewRange.eri);
    // 1 render cell
    draw.save();
    draw.translate(0, -exceptRowTotalHeight);
    viewRange.each((ri, ci) => {
        renderCell(draw, data, ri, ci);
    }, ri => filteredTranslateFunc(ri));
    draw.restore();


    // 2 render mergeCell
    const rset = new Set();
    draw.save();
    draw.translate(0, -exceptRowTotalHeight);
    data.eachMergesInView(viewRange, ({ sri, sci, eri }) => {
        if (!exceptRowSet.has(sri)) {
            renderCell(draw, data, sri, sci);
        } else if (!rset.has(sri)) {
            rset.add(sri);
            const height = data.rows.sumHeight(sri, eri + 1);
            draw.translate(0, -height);
        }
    });
    draw.restore();

    // 3 render autofilter
    renderAutofilter.call(this, viewRange);

    draw.restore();
}

function renderSelectedHeaderCell(x, y, w, h) {
    const { draw } = this;
    draw.save();
    draw.attr({ fillStyle: 'rgba(75, 137, 255, 0.08)' })
        .fillRect(x, y, w, h);
    draw.restore();
}

// viewRange
// type: all | left | top
// w: the fixed width of header
// h: the fixed height of header
// tx: moving distance on x-axis
// ty: moving distance on y-axis
function renderFixedHeaders(type, viewRange, w, h, tx, ty) {
    const { draw, data } = this;
    const sumHeight = viewRange.h; // rows.sumHeight(viewRange.sri, viewRange.eri + 1);
    const sumWidth = viewRange.w; // cols.sumWidth(viewRange.sci, viewRange.eci + 1);
    const nty = ty + h;
    const ntx = tx + w;

    draw.save();
    // draw rect background
    draw.attr(tableFixedHeaderCleanStyle);
    if (type === 'all' || type === 'left') draw.fillRect(0, nty, w, sumHeight);
    if (type === 'all' || type === 'top') draw.fillRect(ntx, 0, sumWidth, h);

    const {
        sri,
        sci,
        eri,
        eci,
    } = data.selector.range;
    // console.log(data.selectIndexes);
    // draw text
    // text font, align...
    draw.attr(tableFixedHeaderStyle());
    // y-header-text
    if (type === 'all' || type === 'left') {
        data.rowEach(viewRange.sri, viewRange.eri, (i, y1, rowHeight) => {
            const y = nty + y1;
            const ii = i;
            draw.line([0, y], [w, y]);
            if (sri <= ii && ii < eri + 1) {
                renderSelectedHeaderCell.call(this, 0, y, w, rowHeight);
            }
            draw.fillText(ii + 1, w / 2, y + (rowHeight / 2));
            if (i > 0 && data.rows.isHide(i - 1)) {
                draw.save();
                draw.attr({ strokeStyle: '#c6c6c6' });
                draw.line([5, y + 5], [w - 5, y + 5]);
                draw.restore();
            }
        });
        draw.line([0, sumHeight + nty], [w, sumHeight + nty]);
        draw.line([w, nty], [w, sumHeight + nty]);
    }
    // x-header-text
    if (type === 'all' || type === 'top') {
        data.colEach(viewRange.sci, viewRange.eci, (i, x1, colWidth) => {
            const x = ntx + x1;
            const ii = i;
            draw.line([x, 0], [x, h]);
            if (sci <= ii && ii < eci + 1) {
                renderSelectedHeaderCell.call(this, x, 0, colWidth, h);
            }
            draw.fillText(stringAt(ii), x + (colWidth / 2), h / 2);
            if (i > 0 && data.cols.isHide(i - 1)) {
                draw.save();
                draw.attr({ strokeStyle: '#c6c6c6' });
                draw.line([x + 5, 5], [x + 5, h - 5]);
                draw.restore();
            }
        });
        draw.line([sumWidth + ntx, 0], [sumWidth + ntx, h]);
        draw.line([0, h], [sumWidth + ntx, h]);
    }
    draw.restore();
}

function renderFixedLeftTopCell(fw, fh) {
    const { draw } = this;
    draw.save();
    // left-top-cell
    draw.attr({ fillStyle: '#f4f5f8' })
        .fillRect(0, 0, fw, fh);
    draw.restore();
}

function renderContentGrid({
    sri,
    sci,
    eri,
    eci,
    w,
    h,
}, fw, fh, tx, ty) {
    const { draw, data } = this;
    const { settings } = data;

    draw.save();
    draw.attr(tableGridStyle)
        .translate(fw + tx, fh + ty);
    // const sumWidth = cols.sumWidth(sci, eci + 1);
    // const sumHeight = rows.sumHeight(sri, eri + 1);
    // console.log('sumWidth:', sumWidth);
    draw.clearRect(0, 0, w, h);
    if (!settings.showGrid) {
        draw.restore();
        return;
    }
    // console.log('rowStart:', rowStart, ', rowLen:', rowLen);
    data.rowEach(sri, eri, (i, y, ch) => {
        // console.log('y:', y);
        if (i !== sri) draw.line([0, y], [w, y]);
        if (i === eri) draw.line([0, y + ch], [w, y + ch]);
    });
    data.colEach(sci, eci, (i, x, cw) => {
        if (i !== sci) draw.line([x, 0], [x, h]);
        if (i === eci) draw.line([x + cw, 0], [x + cw, h]);
    });
    draw.restore();
}

function renderFreezeHighlightLine(fw, fh, ftw, fth) {
    const { draw, data } = this;
    const twidth = data.viewWidth() - fw;
    const theight = data.viewHeight() - fh;
    draw.save()
        .translate(fw, fh)
        .attr({ strokeStyle: 'rgba(75, 137, 255, .6)' });
    draw.line([0, fth], [twidth, fth]);
    draw.line([ftw, 0], [ftw, theight]);
    draw.restore();
}

/** end */
class Table {
    constructor(el, data) {
        this.el = el;
        this.draw = new Draw(el, data.viewWidth(), data.viewHeight());
        this.data = data;
    }

    resetData(data) {
        this.data = data;
        this.render();
    }

    render() {
        // resize canvas
        const { data } = this;
        const { rows, cols } = data;
        // fixed width of header
        const fw = cols.indexWidth;
        // fixed height of header
        const fh = rows.height;

        this.draw.resize(data.viewWidth(), data.viewHeight());
        this.clear();

        const viewRange = data.viewRange();
        // renderAll.call(this, viewRange, data.scroll);
        const tx = data.freezeTotalWidth();
        const ty = data.freezeTotalHeight();
        const { x, y } = data.scroll;
        // 1
        renderContentGrid.call(this, viewRange, fw, fh, tx, ty);
        renderContent.call(this, viewRange, fw, fh, -x, -y);
        renderFixedHeaders.call(this, 'all', viewRange, fw, fh, tx, ty);
        renderFixedLeftTopCell.call(this, fw, fh);
        const [fri, fci] = data.freeze;
        if (fri > 0 || fci > 0) {
            // 2
            if (fri > 0) {
                const vr = viewRange.clone();
                vr.sri = 0;
                vr.eri = fri - 1;
                vr.h = ty;
                renderContentGrid.call(this, vr, fw, fh, tx, 0);
                renderContent.call(this, vr, fw, fh, -x, 0);
                renderFixedHeaders.call(this, 'top', vr, fw, fh, tx, 0);
            }
            // 3
            if (fci > 0) {
                const vr = viewRange.clone();
                vr.sci = 0;
                vr.eci = fci - 1;
                vr.w = tx;
                renderContentGrid.call(this, vr, fw, fh, 0, ty);
                renderFixedHeaders.call(this, 'left', vr, fw, fh, 0, ty);
                renderContent.call(this, vr, fw, fh, 0, -y);
            }
            // 4
            const freezeViewRange = data.freezeViewRange();
            renderContentGrid.call(this, freezeViewRange, fw, fh, 0, 0);
            renderFixedHeaders.call(this, 'all', freezeViewRange, fw, fh, 0, 0);
            renderContent.call(this, freezeViewRange, fw, fh, 0, 0);
            // 5
            renderFreezeHighlightLine.call(this, fw, fh, tx, ty);
        }
    }

    clear() {
        this.draw.clear();
    }
}

export default Table;